﻿namespace DirectoryServicesProtocolsExtensions.MetaData
{
    using System;
    using System.Globalization;
    using System.Security.Principal;

    //todo: add arugment validation similar to DecodeStringEncodedInt
    public static class LdapValueDecoder
    {
        // For some reason value comes in as string as opposed to long, ex "1234556" instead of 123456
        public static object DecodeInt64EncodedTimeValue(object value)
        {
            if (value == null)
            {
                return null;
            }
            
            if (!(value is string))
            {
                throw new ArgumentException("DecodeInt64EncodedTimeValue expects string as an input");
            }

            long fileTimeStampEncoding;

            try
            {
                fileTimeStampEncoding = long.Parse(value.ToString());
            }
            catch (FormatException)
            {
                throw new ArgumentException(
                    string.Format(
                    "DecodeInt64EncodedTimeValue expects string parsable to long. Exception occured during an attempt to parse {0}",
                    value));
            }

            // Handle Never Cases
            if (fileTimeStampEncoding == 0 ||
                fileTimeStampEncoding == -1)
            {
                // PowerShell returns Monday, January 01, 1601 12:00:00 AM in this case,
                // making the result consisten with PowerShell behaviour
                var startOfTimeForPowerShell = new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                return startOfTimeForPowerShell;
            }

            // Handle Infinity Cases
            if (fileTimeStampEncoding == long.MaxValue)
            {
                return DateTime.MaxValue;
            }

            var fileTimeStamp = TimeZoneInfo.ConvertTimeToUtc(DateTime.FromFileTime(fileTimeStampEncoding));
            return new DateTime(
                fileTimeStamp.Year,
                fileTimeStamp.Month,
                fileTimeStamp.Day,
                fileTimeStamp.Hour,
                fileTimeStamp.Minute,
                fileTimeStamp.Second);
        }

        public static object DecodeObjectGUIDValue(object value)
        {
            return new Guid((byte[])value);
        }

        public static object DecodeObjectSidValue(object value)
        {
            return new SecurityIdentifier((byte[])value, 0).ToString();
        }

        public static object DecodeUtcGeneralizedTime(object value)
        {
            var utcTimeEncodedInString = (string)value;
            
            var encodedAsDate = DateTime.ParseExact(
                utcTimeEncodedInString.Replace(".0Z", string.Empty),
                        "yyyyMMddHHmmss",
                        null,
                        DateTimeStyles.AdjustToUniversal);

            return encodedAsDate;
        }

        public static object DecodeGeneralizedTime(object value)
        {
            var utcTimeEncodedInString = (string)value;

            var encodedAsDate = DateTime.ParseExact(
                utcTimeEncodedInString.Replace(".0Z", string.Empty),
                        "yyyyMMddHHmmss",
                        null,
                        DateTimeStyles.None);

            return encodedAsDate;
        }

        public static object DecodeStringEncodedInt(object value)
        {
            var s = value as string;
            if (s != null)
            {
                int convertedInt;
                if (int.TryParse(s, out convertedInt))
                {
                    return convertedInt;
                }
            }

            throw new ArgumentException("Unable to convert string to Int in DecodeStringEncodedInt");
        }
    }
}
